Skip to content

hysteria2: Fix obfs silently disabling socket buffer sizing - #17

Closed
realyxl wants to merge 1 commit into
SagerNet:devfrom
realyxl:hysteria2-obfs-socket-buffer
Closed

realyxl wants to merge 1 commit into
SagerNet:devfrom
realyxl:hysteria2-obfs-socket-buffer

Conversation

@realyxl

@realyxl realyxl commented Aug 1, 2026

Copy link
Copy Markdown

SalamanderPacketConn and GeckoPacketConn embed the net.PacketConn interface, so SetReadBuffer/SetWriteBuffer are not promoted. quic-go gives up on its first assertion:

// sys_conn_buffers.go
conn, ok := c.(interface{ SetReadBuffer(int) error })
if !ok {
    return errors.New("connection doesn't allow setting of receive buffer size. Not a *net.UDPConn?")
}

No syscall is ever issued, so with obfs configured the socket stays at net.core.rmem_default (212992, ~146 packets of depth) instead of quic-go's 8 MB and overflows in bursts. Without obfs the conn arrives as a bare *net.UDPConn and sizing works, which is why the symptom only shows with obfuscation on. Upstream hysteria forwards these methods for the same reason (extras/obfs/conn.go).

Both are re-exposed through common.Cast, which follows Upstream() so client-side wrappers such as bufio.UnbindPacketConn are resolved; this mirrors hysteria/hop.go. SyscallConn is deliberately left out: wrapConn aborts listen/dial when it errors, and the client may run over a non-UDP conn through a detour dialer.

Reproducing quic-go's assertions against a real socket:

SetReadBuffer OOBCapablePacketConn SO_RCVBUF SO_SNDBUF
*net.UDPConn (reference) true true 786896 → 1048576 9216 → 1048576
salamander / gecko, before false false 786896 → 786896 9216 → 9216
salamander / gecko, after true false 786896 → 1048576 9216 → 1048576

OOBCapablePacketConn stays false, so quic-go keeps using ReadFrom/WriteTo and never bypasses deobfuscation. Over a non-UDP conn both return os.ErrInvalid, which wrapConn discards.

Builds and vets clean on linux, android, windows, darwin and freebsd; gofumpt, gofmt -s and gci report no changes.

The salamander.go half applies unchanged to main if you want it on the 1.13.x line.

SalamanderPacketConn and GeckoPacketConn embed the net.PacketConn
interface, which hides SetReadBuffer/SetWriteBuffer. quic-go's type
assertion in setReceiveBuffer/setSendBuffer then fails and the socket
keeps net.core.rmem_default instead of the 8 MB it asks for.

Re-expose both methods, resolving the underlying conn with common.Cast
as hysteria/hop.go does.
@realyxl
realyxl force-pushed the hysteria2-obfs-socket-buffer branch from a0d0808 to 62f707c Compare August 31, 2026 07:55
@nekohasekai
nekohasekai force-pushed the dev branch 3 times, most recently from 7a0d5dc to 497364e Compare September 4, 2026 13:53
@realyxl

realyxl commented Sep 13, 2026

Copy link
Copy Markdown
Author

Closing: this change no longer has any effect on current code.

Since SagerNet/quic-go a7bffd4a ("Probe socket capabilities through syscall.Conn", first shipped in v0.61.0-sing-box-mod.2, 2026-08-06), wrapConn only probes syscall.Conn. For a PacketConn that does not expose a file descriptor it returns basicConn without calling SetReadBuffer/SetWriteBuffer at all ("the socket behind a non-syscall conn is expected to have been configured by its owner"). Forwarding those two methods from the Salamander/Gecko wrappers is therefore dead code now, and forwarding SyscallConn is not an option because the fd-based path would bypass the obfuscation layer.

The remaining gap is on the server side: Service.Start / startWithRealm wrap the listener with the obfuscator without first calling qtls.SetDesiredBufferSizes(conn), which the client side already does when obfs is enabled (hysteria2/client.go). Verified on sing-box 1.14.0 (sing-quic v0.7.0-beta.4, quic-go v0.61.0-sing-box-mod.7): the obfuscated listener socket stays at net.core.rmem_default (208 KiB) and UdpRcvbufErrors grows under load, while the same load through a non-QUIC inbound on the same host drops nothing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant